<template>
  <div class="inline">

    <span @click="openDrawer">
      <a v-if="type === 'link'">{{ action.text }}</a>
      <a-button v-else :type="action.config.color">
        <import-outlined v-if="action.icon === 'import'"/>
        <plus-outlined v-else-if="action.icon === 'plus'"/>
        {{ action.text }}
      </a-button>
    </span>

    <a-drawer v-model:visible="drawerVisible" :closable="false" :destroy-on-close="true" :title="action.config.title"
              placement="right" width="600px" @close="closeDrawer">

      <a-skeleton v-if="initLoading" :paragraph="{ rows: 4 }" :title="false" active/>
      <a-form v-else-if="ready" ref="form" :model="formState" :rules="formRules" layout="vertical"
              @finish="handleFinish">
        <a-form-item v-for="control in controls" :key="control.key" :extra="control.help" :label="control.label"
                     :name="control.key">

          <!-- 文本 -->
          <template v-if="control.type === 'text'">
            <a-input v-model:value.trim="formState[control.key]" :placeholder="control.config.placeholder" allow-clear/>
          </template>

          <template v-if="control.type === 'password'">
            <a-input-password v-model:value.trim="formState[control.key]" :placeholder="control.config.placeholder"
                              allow-clear/>
          </template>

          <!-- 日期 -->
          <template v-else-if="control.type === 'date'">
            <a-date-picker v-model:value="formState[control.key]" :placeholder="control.config.placeholder"
                           style="width: 100%"/>
          </template>

          <!-- 日期 -->
          <template v-else-if="control.type === 'datetime'">
            <a-date-picker v-model:value="formState[control.key]" :placeholder="control.config.placeholder" show-time
                           style="width: 100%"/>
          </template>

          <!-- 文本域 -->
          <template v-else-if="control.type === 'textarea'">
            <a-textarea v-model:value="formState[control.key]" :placeholder="control.config.placeholder"/>
          </template>

          <!-- 单选框 -->
          <template v-else-if="control.type === 'radio'">
            <a-radio-group v-model:value="formState[control.key]">
              <a-radio v-for="option in control.config.options" :key="option.value" :value="option.value">
                {{ option.text }}
              </a-radio>
            </a-radio-group>
          </template>

          <!-- 复选框 -->
          <template v-else-if="control.type === 'checkbox'">
            <a-checkbox-group v-model:value="formState[control.key]" :options="control.config.options"/>
          </template>

          <!-- 树状选择 -->
          <template v-else-if="control.type === 'tree'">
            <a-tree v-model:checked-keys="formState[control.key]" :selectable="false"
                    :tree-data="control.config.options" blockNode
                    checkable>
            </a-tree>
          </template>

          <!-- 开关  -->
          <template v-else-if="control.type === 'switch'">
            <a-switch v-model:checked="formState[control.key]"/>
          </template>

          <!-- 数值 -->
          <template v-else-if="control.type === 'numeric'">
            <!-- 金额 -->
            <template v-if="control.config.format === 'currency'">
              <a-input-number
                v-model:value="formState[control.key]"
                :formatter="value => (value == null || value === '') ? value : `¥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
                :parser="value => value.replace(/¥\s?|(,*)/g, '')"
                :placeholder="control.config.placeholder" style="width: 100%"/>
            </template>

            <!-- 百分比 -->
            <template v-else-if="control.config.format === 'percent'">
              <a-input-number v-model:value="formState[control.key]"
                              :formatter="value => (value == null || value === '') ? value : `${value}%`"
                              :max="100"
                              :min="0" :parser="value => value.replace('%', '')"
                              :placeholder="control.config.placeholder" style="width: 100%"/>
            </template>

            <!-- 普通 -->
            <template v-else>
              <a-input-number v-model:value="formState[control.key]" :min='0' :placeholder="control.config.placeholder"
                              :precision="0" style="width: 100%"/>
            </template>
          </template>

          <!-- 下拉选择 -->
          <template v-else-if="control.type === 'select'">
            <a-select v-model:value="formState[control.key]" :mode="control.config.multiple ? 'multiple' : null"
                      :placeholder="control.config.placeholder"
                      allow-clear style="width: 100%">
              <a-select-option v-for="option in control.config.options" :key="option.value">
                {{ option.text }}
              </a-select-option>
            </a-select>
          </template>

          <!-- 下拉查询 -->
          <template v-else-if="control.type === 'search'">
            <search-control v-model:option="optionState[control.key]" v-model:value="formState[control.key]"
                            :control="control" :placeholder="control.config.placeholder"/>
          </template>

          <!-- 省/市/区级联选择器 -->
          <template v-else-if="control.type === 'region'">
            <region-control v-model:value="formState[control.key]" :control="control"
                            :placeholder="control.config.placeholder"/>
          </template>

          <!-- 文件上传 -->
          <template v-else-if="control.type === 'upload'">
            <upload-control v-model:value="formState[control.key]" :control="control"
                            :init-files="control.config.initFiles" :multiple="control.control?.config?.multiple"
                            :placeholder="control.config.placeholder"/>
          </template>

          <!-- 树选择-->
          <template v-else-if="control.type === 'treeSelect'">
            <a-tree-select v-model:value="formState[control.key]" :control="control"
                           :placeholder="control.config.placeholder"
                           :show-checked-strategy="SHOW_PARENT"
                           :tree-checkable="false" :tree-data="control.config.options" allow-clear
                           style="width: 100%"
                           tree-node-filter-prop="label"/>
          </template>

          <!-- 富文本 -->
          <template v-else-if="control.type === 'richText'">
            <rich-text-editor-control v-model:value="formState[control.key]" :control="control"
                                      :placeholder="control.config.placeholder"/>
          </template>

          <!-- 自定义组件 -->
          <template v-else-if="control.type === 'component'">
            <component :is="control.config.component" v-model:value="formState[control.key]"
                       :control="control"
                       :form="formState" :placeholder="control.config.placeholder" :selected-rows="selectedRows"/>
          </template>
        </a-form-item>

        <a-form-item>
          <a-button :loading="submitLoading" html-type="submit" type="primary">{{ action.config.submitText }}</a-button>
        </a-form-item>
      </a-form>
    </a-drawer>
  </div>
</template>

<script setup>
import {ImportOutlined, PlusOutlined} from "@ant-design/icons-vue";
import {TreeSelect} from 'ant-design-vue';
import dayjs from "dayjs";

const {action, type, selectedRows, extraParams} = defineProps(['action', 'type', 'selectedRows', 'extraParams']);
const emit = defineEmits(['change']);

const {data: initData, isLoading: initLoading, execute: loadInitData} = useApi({
  url: action.config.initUrl,
  immediate: false
});

const formState = reactive(action.config.controls.reduce((prev, control) => {
  const initialValue = control.config?.initialValue;
  if (control.type === 'select' && control.config.multiple) {
    prev[control.key] = initialValue ?? [];
  } else {
    prev[control.key] = initialValue;
  }
  return prev;
}, {}));

const optionState = reactive({});

const SHOW_PARENT = TreeSelect.SHOW_CHILD;

const controls = computed(() => {
  const initDataValue = initData.value;
  return action.config.controls.map(control => {
    const config = {...control.config};
    if (control.type === 'select') {
      const options = config.options;
      if (typeof options === 'string') {
        config.options = initDataValue?.[options]?.map(option => {
          return {
            text: typeof config.textKey === 'function'
              ? config.textKey({option})
              : option[config.textKey],
            value: typeof config.valueKey === 'function'
              ? config.valueKey({option})
              : option[config.valueKey]
          };
        }) ?? [];
      }
    } else if (control.type === 'checkbox') {
      const options = config.options;
      if (typeof options === 'string') {
        config.options = initDataValue?.[options]?.map(option => ({
          label: option[config.textKey],
          value: option[config.valueKey]
        })) ?? [];
      }
    } else if (control.type === 'tree') {
      const options = config.options;
      if (typeof options === 'function') {
        config.options = options();
      }
    } else if (control.type === 'search') {
      const initOptions = config.initOptions;
      if (typeof initOptions === 'string') {
        const options = initDataValue?.[initOptions];
        config.initOptions = options == null
          ? []
          : Array.isArray(options) ? options : [options];
      }
    } else if (control.type === 'upload') {
      const initFiles = config.initFiles;
      const files = initDataValue?.[initFiles];
      config.initFiles = files == null
        ? []
        : Array.isArray(files) ? files : [files];
    } else if (control.type === 'treeSelect') {
      const options = config.options;
      if (typeof options === 'string') {
        config.options = initDataValue?.[options] ?? [];
      }
    }
    config.placeholder = ['date', 'datetime', 'radio', 'select', 'search', 'cascader', 'checkbox'].includes(control.type) ? '选择' + control.label
      : ['upload'].includes(control.type) ? `上传${control.label}`
        : '输入' + control.label;
    return {
      ...control,
      config
    };
  }).filter(control => {
    if (typeof control.visible === 'function') {
      return control.visible({
        form: formState,
        option: optionState
      });
    }
    return true;
  });
});

/* 表单验证规则与默认值 */
const formRules = computed(() => controls.value.reduce((prev, control) => {
  const controlRules = prev[control.key] = prev[control.key] ?? [];
  if (control.required) {
    controlRules.push({
      required: true,
      message: `请${control.config.placeholder}`
    });
  }
  if (control.config?.rules != null) {
    const configRules = typeof control.config.rules === 'string' ? [control.config.rules] : control.config.rules;
    for (const configRule of configRules) {
      if (configRule === 'phoneNumber') {
        controlRules.push({
          pattern: /^1\d{10}$/,
          message: '请输入正确的手机号'
        });
      } else if (configRule === 'email') {
        controlRules.push({
          type: 'email',
          message: '请输入正确的邮箱地址'
        });
      } else if (configRule === 'float') {
        controlRules.push({
          pattern: /^\d+(\.\d+)?$/,
          message: '请输入正确的数字'
        });
      } else if (configRule === 'idNumber') {
        controlRules.push({
          pattern: /(^\d{15})|(^\d{17}(x|X|\d))/,
          message: '请输入正确的身份证'
        });
      } else if (configRule === 'discount') {
        controlRules.push({
          pattern: /^0\.\d$/,
          message: '请输入正确的折扣'
        });
      } else if (configRule === 'url') {
        controlRules.push({
          pattern: "^(https?|ftp)://[^\\s/$.?#].[^\\s]*$",
          message: '请输入正确的url地址'
        });
      } else if (configRule === 'licenseNumber') {
        controlRules.push({
          pattern: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/,
          message: '请输入正确的社会信用代码'
        });
      } else {
        controlRules.push(configRule);
      }
    }
  }
  return prev;
}, {}));

/* 表单初始数据 */
const ready = ref(false);
const init = async () => {
  if (action.config.initUrl == null) { // 没有初始 URL，无需加载初始资源
    ready.value = true;
  } else {
    // 加载初始数据
    const {source} = await loadInitData({
      data: {
        id: selectedRows?.[0]?.id
      }
    });
    if (source) { // 根据表单控件填充表单值
      for (const control of action.config.controls ?? []) {
        let value = source[control.key];
        if (value != null) {
          if (control.type === 'date') {
            value = dayjs(value);
          } else if (control.type === 'datetime') {
            value = dayjs(value);
          }
        }
        formState[control.key] = value;
      }
    }
    ready.value = true;
  }
}

/* 抽屉打开与关闭处理 */
const form = ref(null);
const drawerVisible = ref(false);
const closeDrawer = () => {
  drawerVisible.value = false;
  form.value?.resetFields();
}
const openDrawer = async () => {
  drawerVisible.value = true;
  await init();
}

/* 表单提交 */
const {isLoading: submitLoading, execute: submit} = useApi({
  method: 'POST',
  immediate: false,
  dataType: action.config.dataType
});
const handleFinish = async () => {
  await submit({
    url: typeof action.config.submitUrl === 'string'
      ? action.config.submitUrl
      : action.config.submitUrl({form: formState, option: optionState}),
    data: {
      ...extraParams,
      ...action.config.controls.reduce((prev, control) => {
        let value = formState[control.key];
        if (value != null) {
          if (control.type === 'date') {
            value = value.format('YYYY-MM-DD');
          } else if (control.type === 'datetime') {
            value = value.format('YYYY-MM-DD HH:mm:ss');
          }
        }
        prev[control.key] = value;
        return prev;
      }, {})
    }
  });
  message.success(action.config.submitSuccessMessage);
  closeDrawer();
  emit('change');
};
</script>
